home *** CD-ROM | disk | FTP | other *** search
/ Resource Library: Multimedia / Resource Library: Multimedia.iso / hypercrd / frtxt103.hqx / master FT v1.03 / Free Text XFCN Source Code / zbrowser 0.82.c < prev   
Text File  |  1990-04-12  |  37KB  |  1,406 lines

  1. /* New master XFCN "zbrowser(...)"
  2.  * copyright 1990 - Mark Zimmermann
  3.  *
  4.  * revised 900412 -- filter out ASCII 0-12,14-31, and 127 from returned text
  5.  * so that highlighting when retrieving from non-TEXT files can work reliably
  6.  *
  7.  * For further information, write:
  8.  *    Mark ^Zimmermann
  9.  *    P.O. Box 8310
  10.  *    Silver Spring, MD  20907
  11.  *    USA
  12.  *
  13.  * Be sure to enclose a STAMPED, SELF-ADDRESSED ENVELOPE if you want
  14.  * to receive a timely reply!
  15.  *
  16.  * Electronic addresses:
  17.  *     science@nems.dt.navy.mil
  18.  *     [75066,2044] CompuServe
  19.  */
  20.  
  21.  
  22. /* ------------------------header files to include-------------- */
  23.  
  24. #include <MacTypes.h>
  25. #include <FileMgr.h>
  26. #include <HyperXCmd.h>
  27. #include <SetupA4.h>
  28.  
  29.  
  30. /* ---------------declarations and definitions------------------- */
  31.  
  32.  
  33. /* KEY_LENGTH is the number of letters we have in each index record;
  34.  * 28 is the value chosen for the past year as optimal...don't
  35.  * change it without good reason!
  36.  */
  37. #define KEY_LENGTH    28
  38.  
  39. /* SUBSET_QUANTUM is the distance in bytes that defines the proximity
  40.  * neighborhood of a word in the index ... it is used when defining a
  41.  * subset for proximity searching.... SUBSET_QUANTUM * 8 is the
  42.  * effective compression factor for squeezing the main file down into
  43.  * an array of one-bit flags showing which regions of the database are
  44.  * in the current working subset.
  45.  *
  46.  * Thus, SUBSET_QUANTUM = 32, a nice choice, defines a chunkiness of 32
  47.  * characters in making comparisons for proximity determination purposes,
  48.  * and results in a compression factor of 256.  Thus, in a typical 1 MB Mac
  49.  * with ~100kB free running HyperCard, that should allow subset browsing
  50.  * of databases up to ~25 MB....
  51.  */
  52. #define SUBSET_QUANTUM  32
  53.  
  54. /* structure of the records in the index key file:
  55.  *    a fixed-length character string, padded out with blanks and
  56.  *        containing the unique alphanumeric 'words' in the document
  57.  *        file, changed to all-capital letters;
  58.  *    a cumulative count of how many total occurrences of words, including
  59.  *        the current one, have appeared up to this point in the sorted
  60.  *        index.
  61.  */
  62. typedef struct
  63.   {
  64.     char kkey[KEY_LENGTH];
  65.     long ccount;
  66.   }  KEY_RECORD;
  67.  
  68. /* some symbolic values... */
  69. #define TRUE  1
  70. #define FALSE 0
  71. #define NULL  0
  72.  
  73.  
  74. /* ---------------prototypes------------------- */
  75.  
  76. pascal void main (XCmdBlockPtr paramPtr);
  77. void doAndSubsets (XCmdBlockPtr paramPtr);
  78. void doBooleanNotSubset (XCmdBlockPtr paramPtr);
  79. void doContext (XCmdBlockPtr paramPtr);
  80. void doEmptySubset (XCmdBlockPtr paramPtr);
  81. void doFillSubset (XCmdBlockPtr paramPtr);
  82. void doIndex (XCmdBlockPtr paramPtr);
  83. void doLocate (XCmdBlockPtr paramPtr);
  84. void doNewSubset (XCmdBlockPtr paramPtr);
  85. void doOrSubsets (XCmdBlockPtr paramPtr);
  86. void doReleaseSubset (XCmdBlockPtr paramPtr);
  87. void doSetSubsetBits (XCmdBlockPtr paramPtr);
  88. void doText (XCmdBlockPtr paramPtr);
  89. void returnErrorMsg (XCmdBlockPtr paramPtr, char *msg);
  90. void getKeyRecord (KEY_RECORD *keyRecp, long keyRecNum, int keyFileRefNum);
  91. long getTextPtr (long instanceNum, int ptrFileRefNum);
  92. void getContextLine (long bytes, long textPtr, int refNum, char *ansp);
  93. void buildIndexAnswer (char *ansp, int indexCountWidth, int indexKeyWidth,
  94.   long count, char key[]);
  95. void buildSubIndexAnswer (char *ansp, int indexCountWidth,
  96.   int indexKeyWidth, long maxIndexSampleCount, long prevCcount,
  97.   long thisCcount, char key[], Handle subsetHandle, int ptrFileRefNum);
  98. int inSubset (long textPtr, Handle subsetHandle);
  99. void setSSBit (long textPtr, int setOrClear, Handle subsetHandle);
  100. char *strcpy (char *s1, char *s2);
  101. int strlen (char *s);
  102. int zstrcmp (unsigned char *s1, unsigned char *s2);
  103. long atol (char *s);
  104. void ltoaR (char *ansp, long n, int maxDigits);
  105.  
  106.  
  107. /* ------------------------main program-------------- */
  108.  
  109.  
  110. /* main routine, to dispatch control to a function
  111.  * defined by the first letter of the first argument of the XFCN
  112.  */
  113.  
  114. pascal void main (paramPtr)
  115.   XCmdBlockPtr paramPtr;
  116.   {
  117.     RememberA0 ();
  118.     SetUpA4();
  119.     switch (**(paramPtr->params[0]))
  120.       {
  121.           case 'A':
  122.               doAndSubsets (paramPtr);
  123.               break;
  124.           case 'B':
  125.               doBooleanNotSubset (paramPtr);
  126.               break;
  127.           case 'C':
  128.               doContext (paramPtr);
  129.               break;
  130.           case 'E':
  131.               doEmptySubset (paramPtr);
  132.               break;
  133.           case 'F':
  134.               doFillSubset (paramPtr);
  135.               break;
  136.           case 'I':
  137.               doIndex (paramPtr);
  138.               break;
  139.           case 'L':
  140.               doLocate (paramPtr);
  141.               break;
  142.           case 'N':
  143.               doNewSubset (paramPtr);
  144.               break;
  145.           case 'O':
  146.               doOrSubsets (paramPtr);
  147.               break;
  148.           case 'R':
  149.               doReleaseSubset (paramPtr);
  150.               break;
  151.           case 'S':
  152.               doSetSubsetBits (paramPtr);
  153.               break;
  154.           case 'T':
  155.               doText (paramPtr);
  156.               break;
  157.           default:
  158.               returnErrorMsg (paramPtr,
  159.                 "{Sorry, unrecognized command in TEX zbrowser XFCN call!}");
  160.               break;
  161.       }
  162.     RestoreA4();
  163.     return;
  164.   }
  165.  
  166.  
  167. /* ---------------------major functional units-------------- */
  168.  
  169.  
  170. /* function to logically AND two subsets, and put the resulting intersection
  171.  * of sets into the first of the two
  172.  *
  173.  *    ("ANDSUBSETS", subsetHandle1, subsetHandle2)
  174.  *    --    returns quietly with nothing if it successfully ANDs all bits
  175.  *        in the first subset flag array with the second array; beeps
  176.  *        and gives an error msg if it fails somehow...
  177.  */
  178.  
  179. void doAndSubsets (paramPtr)
  180.   XCmdBlockPtr paramPtr;
  181.   {
  182.     long subsetSize;
  183.     Handle subsetHandle1, subsetHandle2;
  184.     register char *cp1, *cp2, *endOfSubset;
  185.  
  186.     if (paramPtr->paramCount != 3)
  187.       {
  188.           returnErrorMsg (paramPtr,
  189.             "{Sorry, wrong # of parameters in XFCN ANDSUBSETS call!}");
  190.           return;
  191.       }
  192.      
  193.     subsetHandle1 = (Handle) atol (*(paramPtr->params[1]));
  194.     subsetHandle2 = (Handle) atol (*(paramPtr->params[2]));
  195.     
  196.     if (subsetHandle1 == NULL || subsetHandle2 == NULL)
  197.       {
  198.           returnErrorMsg (paramPtr,
  199.             "{Sorry, NULL subsetHandle in XFCN ANDSUBSETS call!}");
  200.           return;
  201.       }
  202.     
  203.     subsetSize = GetHandleSize (subsetHandle1);
  204.     if (subsetSize != GetHandleSize (subsetHandle2))
  205.       {
  206.           returnErrorMsg (paramPtr,
  207.             "{Sorry, inconsistent subsetHandle sizes in XFCN ANDSUBSETS call!}");
  208.           return;
  209.       }
  210.     endOfSubset = *subsetHandle1 + subsetSize;
  211.     for (cp1 = *subsetHandle1, cp2 = *subsetHandle2; cp1 < endOfSubset; ++cp1, ++cp2)
  212.         *cp1 &= *cp2;
  213.         
  214.     return;
  215.   }
  216.  
  217.  
  218. /* function to logically NOT a subset, inverting its contents
  219.  *
  220.  *    ("BOOLEANNOTSUBSET", subsetHandle)
  221.  *    --    returns quietly with nothing if it successfully NOTs all bits
  222.  *        in the subset flag array; beeps
  223.  *        and gives an error msg if it fails somehow...
  224.  */
  225.  
  226. void doBooleanNotSubset (paramPtr)
  227.   XCmdBlockPtr paramPtr;
  228.   {
  229.     long subsetSize;
  230.     Handle subsetHandle;
  231.     register char *cp, *endOfSubset;
  232.  
  233.     if (paramPtr->paramCount != 2)
  234.       {
  235.           returnErrorMsg (paramPtr,
  236.             "{Sorry, wrong # of parameters in XFCN BOOLEANNOTSUBSET call!}");
  237.           return;
  238.       }
  239.      
  240.     subsetHandle = (Handle) atol (*(paramPtr->params[1]));
  241.     
  242.     if (subsetHandle == NULL)
  243.       {
  244.           returnErrorMsg (paramPtr,
  245.             "{Sorry, NULL subsetHandle in XFCN BOOLEANNOTSUBSET call!}");
  246.           return;
  247.       }
  248.     
  249.     subsetSize = GetHandleSize (subsetHandle);
  250.     endOfSubset = *subsetHandle + subsetSize;
  251.     for (cp = *subsetHandle; cp < endOfSubset; ++cp)
  252.         *cp = ~*cp;
  253.         
  254.     return;
  255.   }
  256.  
  257.  
  258. /* function to create the context display...
  259.  *
  260.  *    ("CONTEXT", instanceNum, contextLines, targetContextLine,
  261.  *      contextLineLength, contextWordOffset, maxContextLinesSkipped,
  262.  *    contextGutterWidth, ptrFileRefNum, textFileRefNum, subsetHandle)
  263.  *    --    returns with contextLines of display followed by contextLines
  264.  *        of instanceNum-textPtr pairs, with context instance instanceNum
  265.  *        on line targetContextLine; if contextGutterWidth is non-zero,
  266.  *        then that number of spaces are placed before the key word...
  267.  */
  268.  
  269. void doContext (paramPtr)
  270.   XCmdBlockPtr paramPtr;
  271.   {
  272.     int contextLines, targetContextLine, contextLineLength,
  273.       contextWordOffset, ptrFileRefNum, textFileRefNum, line,
  274.       contextGutterWidth;
  275.     long instanceNum, maxContextLinesSkipped, textPtr, j, *tempNum;
  276.     Handle subsetHandle, answer, tempStor;
  277.     char *ansp;
  278.     register int i;
  279.     register char *cp1, *cp2;
  280.  
  281.     if (paramPtr->paramCount != 11)
  282.       {
  283.           returnErrorMsg (paramPtr,
  284.             "{Sorry, wrong number of parameters in XFCN CONTEXT call!}");
  285.           return;
  286.       }
  287.  
  288.     instanceNum = atol (*(paramPtr->params[1]));
  289.     contextLines = atol (*(paramPtr->params[2]));
  290.     targetContextLine = atol (*(paramPtr->params[3]));
  291.     contextLineLength = atol (*(paramPtr->params[4]));
  292.     contextWordOffset = atol (*(paramPtr->params[5]));
  293.     maxContextLinesSkipped = atol (*(paramPtr->params[6]));
  294.     contextGutterWidth = atol (*(paramPtr->params[7]));
  295.     ptrFileRefNum = atol (*(paramPtr->params[8]));
  296.     textFileRefNum = atol (*(paramPtr->params[9]));
  297.     subsetHandle = (Handle) atol (*(paramPtr->params[10]));
  298.  
  299.     if (instanceNum < 0 || contextLines < 1 || targetContextLine < 1 ||
  300.             targetContextLine > contextLines ||
  301.             contextLineLength < KEY_LENGTH + contextWordOffset ||
  302.             contextWordOffset < 0 ||  maxContextLinesSkipped < 1 ||
  303.             contextGutterWidth > contextWordOffset ||
  304.             ptrFileRefNum == NULL || textFileRefNum == NULL)
  305.       {
  306.           returnErrorMsg (paramPtr,
  307.             "{Sorry, bad parameter in XFCN CONTEXT call!}");
  308.           return;
  309.       }
  310.  
  311.     if ((answer = NewHandle (contextLines * (contextLineLength + 26) + 1))
  312.             == NULL)
  313.       {
  314.           returnErrorMsg (paramPtr,
  315.             "{Sorry, out of memory error in XFCN CONTEXT call!}");
  316.           return;
  317.       }
  318.  
  319.     /* tempStor is used to store values for instanceNums and textPtrs */
  320.     if ((tempStor = NewHandle (contextLines * 2 * sizeof(long))) == NULL)
  321.       {
  322.           DisposHandle (answer);
  323.           returnErrorMsg (paramPtr,
  324.             "{Sorry, secondary out-of-memory error in XFCN CONTEXT call!}");
  325.           return;
  326.       }
  327.  
  328.     /* back up to the correct starting instanceNum */
  329.     for (line = targetContextLine; line > 1; --line)
  330.       {
  331.           for (j = 0; j < maxContextLinesSkipped; ++j)
  332.             {
  333.             textPtr = getTextPtr (--instanceNum, ptrFileRefNum);
  334.             if (subsetHandle == NULL || textPtr < 0 ||
  335.                     inSubset (textPtr, subsetHandle))
  336.                 break;
  337.           }
  338.       }
  339.     
  340.     HLock (answer);
  341.     HLock (tempStor);
  342.     ansp = *answer;
  343.     tempNum = (long *) *tempStor;
  344.  
  345.     /* generate the lines of the context display, saving numbers */
  346.     for (line = 0; line < contextLines; ++line)
  347.       {
  348.           for (j = 0; j < maxContextLinesSkipped; ++j, ++instanceNum)
  349.             {
  350.             textPtr = getTextPtr (instanceNum, ptrFileRefNum);
  351.             if (textPtr < 0)
  352.                 break;
  353.             if (subsetHandle == NULL ||
  354.                     inSubset (textPtr, subsetHandle))
  355.               {
  356.                 getContextLine (contextLineLength,
  357.                     textPtr - contextWordOffset, textFileRefNum, ansp);
  358.                 if (contextGutterWidth > 0)
  359.                   {
  360.                       cp1 = ansp + contextLineLength - 1 - contextGutterWidth;
  361.                       cp2 = ansp + contextLineLength - 1;
  362.                       for (i = contextLineLength - contextWordOffset - contextGutterWidth;
  363.                                   i > 0; --i)
  364.                             *cp2-- = *cp1--;
  365.                       for (i = contextGutterWidth; i > 0; --i)
  366.                           *cp2-- = ' ';
  367.                   }
  368.                 ansp += contextLineLength;
  369.                 break;
  370.               }
  371.           }
  372.         if (j == maxContextLinesSkipped)
  373.           {
  374.               textPtr = -1;
  375.               --instanceNum;
  376.               for (i = contextLineLength; i > 0; --i)
  377.                   *ansp++ = '.';
  378.           }
  379.         tempNum[line] = instanceNum++;
  380.         tempNum[line + contextLines] = textPtr;
  381.         *ansp++ = '\r';
  382.       }
  383.  
  384.     for (line = 0; line < contextLines; ++line)
  385.       {
  386.         ltoaR (ansp, tempNum[line], 12);
  387.         ansp += 12;
  388.         ltoaR (ansp, tempNum[line + contextLines], 12);
  389.         ansp += 12;
  390.         *ansp++ = '\r';
  391.       }
  392.  
  393.     *ansp = '\0';
  394.     HUnlock (answer);
  395.     paramPtr->returnValue = answer;
  396.     HUnlock (tempStor);
  397.     DisposHandle (tempStor);
  398.     return;
  399.   }
  400.  
  401.  
  402. /* function to empty out a subset so that no words are in the valid
  403.  * region:
  404.  *
  405.  *    ("EMPTYSUBSET", subsetHandle)
  406.  *    --    returns quietly with nothing if it successfully sets all bits
  407.  *        in the subset flag array to zero; beeps and gives an error msg
  408.  *        if it fails somehow...
  409.  */
  410.  
  411. void doEmptySubset (paramPtr)
  412.   XCmdBlockPtr paramPtr;
  413.   {
  414.     long subsetSize;
  415.     Handle subsetHandle;
  416.     register char *cp, *endOfSubset;
  417.  
  418.     if (paramPtr->paramCount != 2)
  419.       {
  420.           returnErrorMsg (paramPtr,
  421.             "{Sorry, wrong # of parameters in XFCN EMPTYSUBSET call!}");
  422.           return;
  423.       }
  424.      
  425.     subsetHandle = (Handle) atol (*(paramPtr->params[1]));
  426.     
  427.     if (subsetHandle == NULL)
  428.       {
  429.           returnErrorMsg (paramPtr,
  430.             "{Sorry, NULL subsetHandle in XFCN EMPTYSUBSET call!}");
  431.           return;
  432.       }
  433.     
  434.     subsetSize = GetHandleSize (subsetHandle);
  435.     endOfSubset = *subsetHandle + subsetSize;
  436.     for (cp = *subsetHandle; cp < endOfSubset; ++cp)
  437.         *cp = 0x00;
  438.         
  439.     return;
  440.   }
  441.  
  442.  
  443. /* function to fill a subset so that the entire database is in the
  444.  *    valid region:
  445.  *
  446.  *    ("FILLSUBSET", subsetHandle)
  447.  *    --    returns quietly with nothing if it successfully sets all bits
  448.  *        in the subset flag array to one; beeps and gives an error msg
  449.  *        if failure...
  450.  */
  451.  
  452. void doFillSubset (paramPtr)
  453.   XCmdBlockPtr paramPtr;
  454.   {
  455.     long subsetSize;
  456.     Handle subsetHandle;
  457.     register char *cp, *endOfSubset;
  458.  
  459.     if (paramPtr->paramCount != 2)
  460.       {
  461.           returnErrorMsg (paramPtr,
  462.             "{Sorry, wrong # of parameters in XFCN FILLSUBSET call!}");
  463.           return;
  464.       }
  465.      
  466.     subsetHandle = (Handle) atol (*(paramPtr->params[1]));
  467.     
  468.     if (subsetHandle == NULL)
  469.       {
  470.           returnErrorMsg (paramPtr,
  471.             "{Sorry, NULL subsetHandle in XFCN FILLSUBSET call!}");
  472.           return;
  473.       }
  474.     
  475.     subsetSize = GetHandleSize (subsetHandle);
  476.     endOfSubset = *subsetHandle + subsetSize;
  477.     for (cp = *subsetHandle; cp < endOfSubset; ++cp)
  478.         *cp = 0xFF;
  479.         
  480.     return;
  481.   }
  482.  
  483.  
  484. /* function to produce the index window display and associated info
  485.  *
  486.  *    ("INDEX", wordNum, indexLines, maxIndexSampleCount, indexCountWidth,
  487.  *      indexKeyWidth, keyFileRefNum, ptrFileRefNum, subsetHandle)
  488.  *    --    returns with indexLines of index window display, followed by
  489.  *        indexLines of instanceNums.  The index lines are:
  490.  *        indexCountWidth columns of occurrence count info (right-justified),
  491.  *        a blank column, and indexKeyWidth columns of keyWord (in all
  492.  *        caps, left-justified).  Demand that indexCountWidth be at least
  493.  *        5, to allow for subindex count display, and that indexKeyWidth
  494.  *        be in the range 1 through KEY_LENGTH = 28 ...
  495.  */
  496.  
  497. void doIndex (paramPtr)
  498.   XCmdBlockPtr paramPtr;
  499.   {
  500.     KEY_RECORD thisRec, prevRec;
  501.     register int i;
  502.     int indexLines, keyFileRefNum, indexCountWidth, ptrFileRefNum,
  503.       keyRecsFound, indexKeyWidth;
  504.     long wordNum, maxIndexSampleCount;
  505.     Handle subsetHandle, answer;
  506.     char *ansp;
  507.     
  508.     if (paramPtr->paramCount != 9)
  509.       {
  510.           returnErrorMsg (paramPtr,
  511.             "{Sorry, wrong number of parameters in XFCN INDEX call!}");
  512.           return;
  513.       }
  514.      
  515.     wordNum = atol (*(paramPtr->params[1]));
  516.     indexLines = atol (*(paramPtr->params[2]));
  517.     maxIndexSampleCount = atol (*(paramPtr->params[3]));
  518.     indexCountWidth = atol (*(paramPtr->params[4]));
  519.     indexKeyWidth = atol (*(paramPtr->params[5]));
  520.     keyFileRefNum = atol (*(paramPtr->params[6]));
  521.     ptrFileRefNum = atol (*(paramPtr->params[7]));
  522.     subsetHandle = (Handle) atol (*(paramPtr->params[8]));
  523.     
  524.     if (wordNum < 0 || indexLines < 1 || maxIndexSampleCount < 1 ||
  525.             indexCountWidth < 5 || indexKeyWidth < 1 ||
  526.              indexKeyWidth > KEY_LENGTH || keyFileRefNum == 0 ||
  527.              ptrFileRefNum == 0)
  528.       {
  529.           returnErrorMsg (paramPtr,
  530.             "{Sorry, bad parameter in XFCN INDEX call!}");
  531.           return;
  532.       }
  533.  
  534.     
  535.     if ((answer = NewHandle (indexLines *
  536.                     (indexCountWidth + indexKeyWidth + 15) + 1)) == NULL)
  537.       {
  538.           returnErrorMsg (paramPtr,
  539.             "{Sorry, out of memory error in XFCN INDEX call!}");
  540.           return;
  541.       }
  542.  
  543.     HLock (answer);
  544.     ansp = *answer;
  545.  
  546.     getKeyRecord (&prevRec, wordNum - 1, keyFileRefNum);
  547.     
  548.     for (i = 0; i < indexLines; ++i)
  549.       {
  550.         getKeyRecord (&thisRec, wordNum + i, keyFileRefNum);
  551.         if (thisRec.ccount == 0)
  552.             break;
  553.             
  554.         if (subsetHandle == NULL)
  555.             buildIndexAnswer (ansp, indexCountWidth, indexKeyWidth,
  556.                         thisRec.ccount - prevRec.ccount, thisRec.kkey);
  557.         else
  558.             buildSubIndexAnswer (ansp, indexCountWidth, indexKeyWidth,
  559.                 maxIndexSampleCount, prevRec.ccount, thisRec.ccount,
  560.                 thisRec.kkey, subsetHandle, ptrFileRefNum);
  561.                         
  562.         ansp += indexCountWidth + indexKeyWidth + 2;
  563.         prevRec.ccount = thisRec.ccount;
  564.       }
  565.     
  566.     keyRecsFound = i;
  567.     for (i = keyRecsFound; i < indexLines; ++i)
  568.         *ansp++ = '\r';
  569.  
  570.     for (i = 0; i < keyRecsFound; ++i)
  571.       {
  572.         getKeyRecord (&thisRec, wordNum + i - 1, keyFileRefNum);
  573.         ltoaR (ansp, thisRec.ccount, 12);
  574.         ansp += 12;
  575.         *ansp++ = '\r';
  576.       }
  577.  
  578.     for (i = keyRecsFound; i < indexLines; ++i)
  579.         *ansp++ = '\r';
  580.     
  581.     *ansp = '\0';
  582.     HUnlock (answer);
  583.     paramPtr->returnValue = answer;
  584.     return;
  585.   }
  586.  
  587.  
  588. /* function to find a chosen string in the index key file (just do a
  589.  * binary search to locate it):
  590.  *
  591.  *    ("LOCATE", targetString, keyFileRefNum)
  592.  *    --    returns wordNum for the targetString if it is found in the
  593.  *        key file; otherwise returns wordNum for the word alphabetically
  594.  *        preceding targetString followed by "{targetString not found!}"
  595.  *        on the second line of the answer...
  596.  *
  597.  */
  598.  
  599. void doLocate (paramPtr)
  600.   XCmdBlockPtr paramPtr;
  601.   {
  602.       register int i, c;
  603.       int keyFileRefNum, diff;
  604.       char *cp;
  605.       register long mid;
  606.     long low, high, keyFileSize;
  607.     KEY_RECORD thisRec, targetRec;
  608.     Handle answer;
  609.     
  610.     if (paramPtr->paramCount != 3)
  611.       {
  612.           returnErrorMsg (paramPtr,
  613.             "{Sorry, wrong number of parameters in XFCN LOCATE call!}");
  614.           return;
  615.       }
  616.  
  617.     keyFileRefNum = atol (*(paramPtr->params[2]));
  618.  
  619.     if (keyFileRefNum == NULL)
  620.       {
  621.           returnErrorMsg (paramPtr,
  622.             "{Sorry, NULL keyFileRefNum error in XFCN LOCATE call!}");
  623.           return;
  624.       }
  625.  
  626.     cp = *(paramPtr->params[1]);
  627.     for (i = 0; i < KEY_LENGTH; ++i)
  628.       {
  629.           c = *cp;
  630.           if (c == '\0')
  631.             {
  632.               targetRec.kkey[i] = ' ';
  633.               continue;
  634.             }
  635.           if (c >= 'a' && c <= 'z')
  636.               c = c - 'a' + 'A';
  637.           targetRec.kkey[i] = c;
  638.           ++cp;
  639.       }
  640.       
  641.     low = 0;
  642.     GetEOF (keyFileRefNum, &keyFileSize);
  643.     high = keyFileSize / sizeof (KEY_RECORD) - 1;
  644.  
  645.     while (low <= high)
  646.       {
  647.         mid = (low + high) / 2;
  648.         getKeyRecord (&thisRec, mid, keyFileRefNum);
  649.         if (thisRec.ccount == 0)
  650.           {
  651.               returnErrorMsg (paramPtr,
  652.                 "{Sorry, possible file I/O error in XFCN LOCATE call!}");
  653.               return;
  654.           }
  655.         diff = zstrcmp ((unsigned char *)targetRec.kkey,
  656.                         (unsigned char *)thisRec.kkey);
  657.         if (diff < 0)
  658.             high = mid - 1;
  659.         else if (diff > 0)
  660.             low = mid + 1;
  661.         else
  662.           break;
  663.       }
  664.  
  665.     if (diff < 0)
  666.         --mid;
  667.     if (mid < 0)
  668.         mid = 0;
  669.     
  670.     if ((answer = NewHandle (64)) == NULL)
  671.       {
  672.           returnErrorMsg (paramPtr,
  673.             "{Sorry, out of memory error in XFCN LOCATE call!}");
  674.           return;
  675.       }
  676.     ltoaR (*answer, mid, 12);
  677.     *(*answer + 12) = '\0';
  678.     if (diff != 0)
  679.         strcpy (*answer + 12, "\r{target string not found!}");
  680.     paramPtr->returnValue = answer;
  681.     return;
  682.   }
  683.  
  684.  
  685. /* function to create a new subset:
  686.  *
  687.  *    ("NEWSUBSET", textFileRefNum)
  688.  *    --    returns subsetHandle for a new subset that it creates, big
  689.  *        enough to do subset browsing -- but does NOT initialize that
  690.  *        subset or check to see whether another subset already
  691.  *        exists.  Beeps and gives error msg if it fails...
  692.  */
  693.  
  694. void doNewSubset (paramPtr)
  695.   XCmdBlockPtr paramPtr;
  696.   {
  697.     int textFileRefNum;
  698.     long textFileSize, subsetSize;
  699.     Handle subsetHandle, answer;
  700.     
  701.     if (paramPtr->paramCount != 2)
  702.       {
  703.           returnErrorMsg (paramPtr,
  704.             "{Sorry, wrong # of parameters in XFCN NEWSUBSET call!}");
  705.           return;
  706.       }
  707.  
  708.     textFileRefNum = atol (*(paramPtr->params[1]));
  709.     if (textFileRefNum == NULL ||
  710.         GetEOF (textFileRefNum, &textFileSize) != noErr)
  711.       {
  712.           returnErrorMsg (paramPtr,
  713.             "{Sorry, file error in XFCN NEWSUBSET call!}");
  714.           return;
  715.       }
  716.  
  717.     subsetSize = 1 + textFileSize / (SUBSET_QUANTUM * 8);
  718.     
  719.     if ((subsetHandle = NewHandle (subsetSize)) == NULL)
  720.       {
  721.           returnErrorMsg (paramPtr,
  722.             "{Sorry, not enough memory for subset creation!}");
  723.           return;
  724.       }
  725.  
  726.     if ((answer = NewHandle (16)) == NULL)
  727.       {
  728.         DisposHandle (subsetHandle);
  729.           returnErrorMsg (paramPtr,
  730.             "{Sorry, out of memory error in XFCN NEWSUBSET call!}");
  731.           return;
  732.       }
  733.     ltoaR (*answer, (long)subsetHandle, 12);
  734.     *(*answer + 12) = '\0';
  735.     paramPtr->returnValue = answer;
  736.  
  737.     return;
  738.     
  739.   }
  740.  
  741.  
  742. /* function to logically OR two subsets, and put the resulting union
  743.  * of sets into the first of the two
  744.  *
  745.  *    ("ORSUBSETS", subsetHandle1, subsetHandle2)
  746.  *    --    returns quietly with nothing if it successfully ORs all bits
  747.  *        in the first subset flag array with the second array; beeps
  748.  *        and gives an error msg if it fails somehow...
  749.  */
  750.  
  751. void doOrSubsets (paramPtr)
  752.   XCmdBlockPtr paramPtr;
  753.   {
  754.     long subsetSize;
  755.     Handle subsetHandle1, subsetHandle2;
  756.     register char *cp1, *cp2, *endOfSubset;
  757.  
  758.     if (paramPtr->paramCount != 3)
  759.       {
  760.           returnErrorMsg (paramPtr,
  761.             "{Sorry, wrong # of parameters in XFCN ORSUBSETS call!}");
  762.           return;
  763.       }
  764.      
  765.     subsetHandle1 = (Handle) atol (*(paramPtr->params[1]));
  766.     subsetHandle2 = (Handle) atol (*(paramPtr->params[2]));
  767.     
  768.     if (subsetHandle1 == NULL || subsetHandle2 == NULL)
  769.       {
  770.           returnErrorMsg (paramPtr,
  771.             "{Sorry, NULL subsetHandle in XFCN ORSUBSETS call!}");
  772.           return;
  773.       }
  774.     
  775.     subsetSize = GetHandleSize (subsetHandle1);
  776.     if (subsetSize != GetHandleSize (subsetHandle2))
  777.       {
  778.           returnErrorMsg (paramPtr,
  779.             "{Sorry, inconsistent subsetHandle sizes in XFCN ORSUBSETS call!}");
  780.           return;
  781.       }
  782.     endOfSubset = *subsetHandle1 + subsetSize;
  783.     for (cp1 = *subsetHandle1, cp2 = *subsetHandle2; cp1 < endOfSubset; ++cp1, ++cp2)
  784.         *cp1 |= *cp2;
  785.         
  786.     return;
  787.   }
  788.  
  789.  
  790. /* routine to get rid of a subset and release that memory:
  791.  *
  792.  *    ("RELEASESUBSET", subsetHandle)
  793.  *    --    returns quietly with nothing if successful in releasing the
  794.  *        subsetHandle, or noisily with an error message if it fails...
  795.  */
  796.  
  797. void doReleaseSubset (paramPtr)
  798.   XCmdBlockPtr paramPtr;
  799.   {
  800.     Handle subsetHandle;
  801.     
  802.     if (paramPtr->paramCount != 2)
  803.       {
  804.           returnErrorMsg (paramPtr,
  805.             "{Sorry, wrong # of params in XFCN RELEASESUBSET call!}");
  806.           return;
  807.       }
  808.  
  809.     subsetHandle = (Handle) atol (*(paramPtr->params[1]));
  810.     if (subsetHandle == NULL)
  811.       {
  812.           returnErrorMsg (paramPtr,
  813.             "{Sorry, NULL subsetHandle in XFCN RELEASESUBSET call!}");
  814.           return;
  815.       }
  816.     
  817.     DisposHandle (subsetHandle);
  818.     return;
  819.   }
  820.  
  821.  
  822. /* function to turn on or off bits in a subset according to their
  823.  * proximity to a given word's occurrences:
  824.  *
  825.  *    ("SETSUBSETBITS", wordNum, neighborhoodSize, setOrClear,
  826.  *      keyFileRefNum, ptrFileRefNum, subsetHandle)
  827.  *    --    returns quietly with nothing if it is successful in setting or
  828.  *        clearing (depending on setOrClear's value, 0 or non-0) the
  829.  *        bits in the subset flag array in the neighborhood of the
  830.  *        chosen word(s); gives an error msg if there was a problem.
  831.  *        neighborhoodSize is in characters and is used to determine
  832.  *         how many bits to set/clear on each side of the instances...
  833.  */
  834.  
  835. void doSetSubsetBits (paramPtr)
  836.   XCmdBlockPtr paramPtr;
  837.   {
  838.     long wordNum, neighborhoodSize, bitsToSet, maxTextPtr, instance,
  839.       tp0, tp, tpMax;
  840.     int setOrClear, keyFileRefNum, ptrFileRefNum;
  841.     Handle subsetHandle;
  842.     KEY_RECORD prevRec, thisRec;
  843.     
  844.     if (paramPtr->paramCount != 7)
  845.       {
  846.           returnErrorMsg (paramPtr,
  847.             "{Sorry, wrong # of parameters in XFCN SETSUBSET call!}");
  848.           return;
  849.       }
  850.  
  851.      
  852.     wordNum = atol (*(paramPtr->params[1]));
  853.     neighborhoodSize = atol (*(paramPtr->params[2]));
  854.     setOrClear = atol (*(paramPtr->params[3]));
  855.     keyFileRefNum = atol (*(paramPtr->params[4]));
  856.     ptrFileRefNum = atol (*(paramPtr->params[5]));
  857.     subsetHandle = (Handle) atol (*(paramPtr->params[6]));
  858.  
  859.     if (wordNum < 0 || neighborhoodSize < 1 ||
  860.             keyFileRefNum == NULL || ptrFileRefNum == NULL ||
  861.             subsetHandle == NULL)
  862.       {
  863.           returnErrorMsg (paramPtr,
  864.             "{Sorry, bad parameter in XFCN SETSUBSET call!}");
  865.           return;
  866.       }
  867.  
  868.     bitsToSet = (neighborhoodSize * 2) / SUBSET_QUANTUM + 1;
  869.     maxTextPtr = GetHandleSize (subsetHandle) * SUBSET_QUANTUM * 8;
  870.     getKeyRecord (&prevRec, wordNum - 1, keyFileRefNum);
  871.     getKeyRecord (&thisRec, wordNum, keyFileRefNum);
  872.     
  873.     for (instance = prevRec.ccount; instance < thisRec.ccount; ++instance)
  874.       {
  875.         tp0 = getTextPtr (instance, ptrFileRefNum);
  876.         if (tp0 < 0)
  877.           {
  878.               returnErrorMsg (paramPtr,
  879.                 "{Sorry, getTextPtr I/O error in XFCN SETSUBSET call!}");
  880.               return;
  881.           }
  882.         tp = tp0 - (bitsToSet / 2) * SUBSET_QUANTUM;
  883.         tpMax = tp + bitsToSet * SUBSET_QUANTUM;
  884.         if (tp < 0)
  885.             tp = 0;
  886.         if (tpMax > maxTextPtr)
  887.             tpMax = maxTextPtr;
  888.         for ( ; tp < tpMax; tp += SUBSET_QUANTUM)
  889.             setSSBit (tp, setOrClear, subsetHandle);
  890.       }
  891.     
  892.     return;    
  893.   }
  894.  
  895.  
  896. /* function to grab a chunk of text:
  897.  *
  898.  *    ("TEXT", textPtr, textChunkSize, textOffset, textFileRefNum)
  899.  *    --    returns with (if possible; see below)
  900.  *        textChunkSize bytes of text from the text file,
  901.  *        starting at byte number textPtr-textOffset+1 and ending
  902.  *        just before byte number textPtr-textOffset+textChunkSize+1.
  903.  *        (The '+1' is to match up with HyperCard's 1-based counting
  904.  *        convention, rather than the 0-based C convention!!)
  905.  *        If the file isn't big enough or if textPtr is too near the
  906.  *        beginning or end of the file, cut off the retrieved text
  907.  *        at that boundary and insert the words {beginning of database}
  908.  *        or {end of database}.  FILTER OUT ANY '\0' characters in
  909.  *        the text that is returned, to avoid problems; also
  910.  *        filter out any tabs, since HC mistreats them in its display;
  911.  *        and turn any linefeeds ('\n' = 0x0A) to returns ('\r' = 0x0D),
  912.  *        for compatibility in reading indexed files from UNIX hosts.
  913.  *        ((mod 900412 -- filter out any controls: 0-12,14-31,127))
  914.  *        Restrict textChunkSize to <32000 bytes.  After the text, on
  915.  *        a separate line, return three numbers:  the byte number of
  916.  *        the first char returned relative to the beginning of the text
  917.  *        file, the actual offset within the characters returned
  918.  *        of the originally-requested textPtr, and the byte number
  919.  *        of the character after the last char returned relative to
  920.  *        the beginning of the text file.
  921.  */
  922.  
  923. void doText (paramPtr)
  924.   XCmdBlockPtr paramPtr;
  925.   {
  926.     int textFileRefNum;
  927.     long textPtr, textChunkSize, textOffset, textFileSize, startText,
  928.       endText, count;
  929.     Handle answer;
  930.     register char *ansp, *cp;
  931.     
  932.     if (paramPtr->paramCount != 5)
  933.       {
  934.           returnErrorMsg (paramPtr,
  935.               "Sorry, wrong number of parameters in XFCN TEXT call!}");
  936.           return;
  937.       }
  938.      
  939.     textPtr = atol (*(paramPtr->params[1]));
  940.     textChunkSize = atol (*(paramPtr->params[2]));
  941.     textOffset = atol (*(paramPtr->params[3]));
  942.     textFileRefNum = atol (*(paramPtr->params[4]));
  943.  
  944.     GetEOF (textFileRefNum, &textFileSize);
  945.  
  946.     if (textPtr < 0 || textPtr > textFileSize || textOffset < 1 ||
  947.          textOffset > textChunkSize || textChunkSize < 1 ||
  948.          textChunkSize > 32000 || textFileRefNum == 0)
  949.       {
  950.           returnErrorMsg (paramPtr,
  951.               "{Sorry, bad parameter in XFCN TEXT call!}");
  952.           return;
  953.       }
  954.     
  955.     startText = textPtr - textOffset + 1;
  956.     if (startText < 0)
  957.         startText = 0;
  958.     endText = textPtr + textChunkSize - textOffset + 1;
  959.     if (endText > textFileSize)
  960.         endText = textFileSize;
  961.     
  962.     count = endText - startText;
  963.     if ((answer = NewHandle (count + 80)) == NULL)
  964.       {
  965.           returnErrorMsg (paramPtr,
  966.               "{Sorry, out of memory error in XFCN TEXT call!}");
  967.           return;
  968.       }
  969.  
  970.     HLock (answer);
  971.     ansp = *answer;
  972.     if (startText == 0)
  973.       {
  974.           strcpy (ansp, "{beginning of database}\r");
  975.           ansp += strlen ("{beginning of database}\r");
  976.           textOffset = textPtr + strlen ("{beginning of database}\r") + 1;
  977.       }
  978.  
  979.     if (SetFPos (textFileRefNum, fsFromStart, startText) != noErr ||
  980.             FSRead (textFileRefNum, &count, ansp) != noErr)
  981.       {
  982.           SysBeep (10);
  983.         strcpy (ansp,
  984.             "{Sorry, file I/O error in XFCN TEXT call!}");
  985.         HUnlock (answer);
  986.         paramPtr->returnValue = answer;
  987.         return;
  988.       }
  989.     
  990.     cp = ansp;
  991.     ansp += count;
  992.     
  993.     for ( ; cp < ansp; ++cp)
  994.       {
  995.         if (*cp == '\n' && *(cp-1) == '\r')
  996.             *cp = ' ';
  997.         if (*cp == '\n')
  998.             *cp = '\r';
  999.         if ((*cp >= 0 && *cp < '\r') || (*cp > '\r' && *cp < ' ') || *cp == 127)
  1000.             *cp = ' ';
  1001.       }
  1002.     
  1003.     *ansp++ = '\r';
  1004.     if (endText == textFileSize)
  1005.       {
  1006.           strcpy (ansp, "{end of database}\r");
  1007.           ansp += strlen ("{end of database}\r");
  1008.       }
  1009.     ltoaR (ansp, startText, 12);
  1010.     ansp += 12;
  1011.     ltoaR (ansp, textOffset, 12);
  1012.     ansp += 12;
  1013.     ltoaR (ansp, endText, 12);
  1014.     ansp += 12;
  1015.     *ansp = '\0';
  1016.     HUnlock (answer);
  1017.     paramPtr->returnValue = answer;
  1018.     return;
  1019.   }
  1020.  
  1021.  
  1022.  
  1023. /* ------------------------support routines-------------- */
  1024.  
  1025.  
  1026. /* function to set the return value of the XFCN to a chosen error msg;
  1027.  * if there isn't enough free memory to give us a Handle to the msg,
  1028.  * beep a bunch and then return!
  1029.  */
  1030.  
  1031. void returnErrorMsg (paramPtr, msg)
  1032.   XCmdBlockPtr paramPtr;
  1033.   char *msg;
  1034.   {
  1035.     Handle answer;
  1036.     int msgLength;
  1037.     
  1038.     SysBeep (10);
  1039.     msgLength = strlen (msg);
  1040.     if ((answer = NewHandle (1 + msgLength)) == NULL)
  1041.       {
  1042.         SysBeep (10);
  1043.         SysBeep (10);
  1044.         SysBeep (10);
  1045.         SysBeep (10);
  1046.         SysBeep (10);
  1047.         return;
  1048.       }
  1049.  
  1050.     strcpy (*answer, msg);
  1051.     paramPtr->returnValue = answer;
  1052.     return;
  1053.   }
  1054.  
  1055.  
  1056. /* function to fetch an index key record from the key file; if an
  1057.  * illegal keyRecNum is asked for, or if any sort of I/O error is
  1058.  * reported by SetFPos() or FSRead(), return 0 ccount and blank kkey....
  1059.  */
  1060.  
  1061. void getKeyRecord (keyRecp, keyRecNum, keyFileRefNum)
  1062.   KEY_RECORD *keyRecp;
  1063.   long keyRecNum;
  1064.   int keyFileRefNum;
  1065.   {
  1066.     long count;
  1067.     register int i;
  1068.  
  1069.     count = sizeof(KEY_RECORD);
  1070.             
  1071.     if (keyRecNum < 0 ||
  1072.             SetFPos (keyFileRefNum, fsFromStart, 
  1073.                     keyRecNum * sizeof(KEY_RECORD)) != noErr ||
  1074.             FSRead (keyFileRefNum, &count, keyRecp) != noErr)
  1075.       {
  1076.         for (i = 0; i < KEY_LENGTH; ++i)
  1077.             keyRecp->kkey[i] = ' ';
  1078.           keyRecp->ccount = 0;
  1079.       }
  1080.  
  1081.     return;
  1082.   }
  1083.  
  1084. /* function to fetch the value of the nth ptr from file ptrFileRefNum;
  1085.  * return illegal value (-1) for result if something goes wrong....
  1086.  */
  1087.  
  1088. long getTextPtr (n, ptrFileRefNum)
  1089.   long n;
  1090.   int ptrFileRefNum;
  1091.   {
  1092.     long bytes = sizeof(long), result;
  1093.     
  1094.     if (SetFPos (ptrFileRefNum, fsFromStart, n * sizeof(long)) != noErr ||
  1095.             FSRead (ptrFileRefNum, &bytes, &result) != noErr)
  1096.         return (-1);
  1097.  
  1098.     return (result);
  1099.   }
  1100.  
  1101.  
  1102. /* function to fetch a filtered line of text from the file ... fill
  1103.  * in with blanks if try to fetch from before the beginning of the
  1104.  * file or after the end of the file ... filter all control characters
  1105.  * by turning them into spaces ....
  1106.  *
  1107.  * modified 880917 to allow display of accented, etc. characters, by fixing
  1108.  * the test in the final 'if' statement to work properly with 'signed'
  1109.  * characters, and modified to retrieve properly at end of database
  1110.  */
  1111.  
  1112. void getContextLine (bytes, start, refNum, ans)
  1113.   int refNum;
  1114.   long bytes, start;
  1115.   register char *ans;
  1116.   {
  1117.     register int i = 0;
  1118.     int errno;
  1119.     long origbytes;
  1120.     
  1121.     origbytes = bytes;
  1122.     if (start < 0)
  1123.         for (i = 0; i < -start; ++i)
  1124.             ans[i] = ' ';
  1125.     bytes -= i;
  1126.     
  1127.     if (SetFPos (refNum, fsFromStart, start + i) != noErr ||
  1128.             ((errno = FSRead (refNum, &bytes, ans + i)) != noErr &&
  1129.               errno != eofErr))
  1130.       {
  1131.         if (origbytes >
  1132.                 strlen ("{Sorry, file I/O error in XFCN CONTEXT call!}"))
  1133.           {
  1134.             strcpy (ans, "{Sorry, file I/O error in XFCN CONTEXT call!}");
  1135.             bytes = origbytes;
  1136.           }
  1137.         else
  1138.             SysBeep (10);
  1139.       }
  1140.     
  1141.     if (bytes + i < origbytes)
  1142.         for (i += bytes; i < origbytes; ++i)
  1143.             ans[i] = ' ';
  1144.  
  1145.     for (i = 0; i < origbytes; ++i)
  1146.           if ((ans[i] >= 0 && ans[i] < 32) || ans[i] == 127)
  1147.               ans[i] = ' ';
  1148.         
  1149.     return;
  1150.   }
  1151.  
  1152.  
  1153. /* function to format an index record, with the count right-justified
  1154.  * followed by a space, then the key word itself, followed by a '\r'.
  1155.  */
  1156.  
  1157. void buildIndexAnswer (ansp, cwidth, kwidth, count, key)
  1158.   char *ansp, *key;
  1159.   long count;
  1160.   int cwidth, kwidth;
  1161.   {
  1162.     register int i;
  1163.  
  1164.     ltoaR (ansp, count, cwidth);
  1165.     ansp += cwidth;
  1166.     *ansp++ = ' ';
  1167.     for (i = 0; i < kwidth; ++i)
  1168.         *ansp++ = *key++;
  1169.     *ansp = '\r';
  1170.     
  1171.     return;
  1172.   }
  1173.  
  1174.  
  1175. /* function to format an index record when working in a subset; like
  1176.  * buildIndexAnswer function above, but with information about how many
  1177.  * instances of each word are in the working subset.  Specifically,
  1178.  * give a percentage estimate based on the last maxIndexSampleCount
  1179.  * instances for a word that occurs more than maxIndexSampleCount times
  1180.  * (e.g., " ~37% "), and for less frequently occurring words give the
  1181.  * actual fraction of valid/total instances (e.g., " 17/49 ").
  1182.  */
  1183.  
  1184. void buildSubIndexAnswer (ansp, cwidth, kwidth, maxSample,
  1185.             prevCcount, thisCcount, key, subsetHandle, ptrFileRefNum)
  1186.   char *ansp, *key;
  1187.   int cwidth, kwidth, ptrFileRefNum;
  1188.   long maxSample, prevCcount, thisCcount;
  1189.   Handle subsetHandle;
  1190.   {
  1191.       long startCcount, instance, goodInstances;
  1192.       register int i;
  1193.       int goodPercent, subWidth;
  1194.       
  1195.     if (thisCcount - prevCcount > maxSample)
  1196.         startCcount = thisCcount - maxSample;
  1197.     else
  1198.         startCcount = prevCcount;
  1199.     
  1200.     goodInstances = 0;
  1201.     for (instance = startCcount; instance < thisCcount; ++instance)
  1202.         if (inSubset (getTextPtr (instance, ptrFileRefNum),
  1203.                             subsetHandle))
  1204.             ++goodInstances;
  1205.     
  1206.     if (thisCcount - prevCcount > maxSample)
  1207.       {
  1208.         goodPercent = (100 * goodInstances) / (thisCcount - startCcount);
  1209.         *ansp++ = '~';
  1210.         ltoaR (ansp, goodPercent, 3);
  1211.         ansp += 3;
  1212.         *ansp++ = '%';
  1213.         for (i = 5; i < cwidth; ++i)
  1214.             *ansp++ = ' ';
  1215.       }
  1216.     else
  1217.       {
  1218.         subWidth = (cwidth - 1) / 2;
  1219.         ltoaR (ansp, goodInstances, subWidth);
  1220.         ansp += subWidth;
  1221.         *ansp++ = '/';
  1222.         ltoaR (ansp, thisCcount - prevCcount, subWidth);
  1223.         ansp += subWidth;
  1224.         for (i = 2 * subWidth + 1; i < cwidth; ++i)
  1225.             *ansp++ = ' ';
  1226.       }
  1227.     
  1228.     *ansp++ = ' ';
  1229.     for (i = 0; i < kwidth; ++i)
  1230.         *ansp++ = *key++;
  1231.     *ansp = '\r';
  1232.  
  1233.     return;
  1234.   }
  1235.  
  1236.  
  1237. /* function to determine if a given textPtr is in the subset of
  1238.  * interest ... do it by a simple computation and look-up in the
  1239.  * bit array of subset flags...
  1240.  */
  1241.  
  1242. int inSubset (textPtr, subsetHandle)
  1243.   long textPtr;
  1244.   Handle subsetHandle;
  1245.   {
  1246.     int bitNum;
  1247.     long byteNum;
  1248.  
  1249.     bitNum = (textPtr % (8 * SUBSET_QUANTUM)) / SUBSET_QUANTUM;
  1250.     byteNum = textPtr / (8 * SUBSET_QUANTUM);
  1251.  
  1252.     return (((*subsetHandle)[byteNum] >> bitNum) & 1);
  1253.   }
  1254.  
  1255.  
  1256. /* function to set or clear a bit in the subset array...
  1257.  */
  1258.  
  1259. void setSSBit (textPtr, setOrClear, subsetHandle)
  1260.   long textPtr;
  1261.   int setOrClear;
  1262.   Handle subsetHandle;
  1263.   {
  1264.     int bitNum;
  1265.     long byteNum;
  1266.  
  1267.     bitNum = (textPtr % (8 * SUBSET_QUANTUM)) / SUBSET_QUANTUM;
  1268.     byteNum = textPtr / (8 * SUBSET_QUANTUM);
  1269.  
  1270.     if (setOrClear)
  1271.         *(*subsetHandle + byteNum) |= 1 << bitNum;
  1272.     else
  1273.         *(*subsetHandle + byteNum) &= ~(1 << bitNum);
  1274.     
  1275.     return;
  1276.   }
  1277.  
  1278.  
  1279. /* function to copy a string from one place to another, in a rather
  1280.  * obvious fashion ... adapted from K&R p.101 ....
  1281.  */
  1282.  
  1283. char *strcpy (s1, s2)
  1284.   register char *s1, *s2;
  1285.   {
  1286.     char *s = s1;
  1287.     
  1288.     while (*s1++ = *s2++)
  1289.         ;
  1290.     return (s);
  1291.   }
  1292.  
  1293.  
  1294. /* function to determine the length of a string ... standard thing,
  1295.  * adapted from K&R p.98 ....
  1296.  */
  1297.  
  1298. int strlen (s)
  1299.   register char *s;
  1300.   {
  1301.     char *s0 = s;
  1302.  
  1303.     while (*s++)
  1304.         ;
  1305.     return (s - s0 - 1);
  1306.   }
  1307.  
  1308. /* my function to compare two strings and give a result as to who is
  1309.  * alphabetically earlier.  Note that this is almost the same as strncmp()
  1310.  * with the fixed value of KEY_LENGTH as the maximum comparison distance,
  1311.  * except that I must be sure to handle the non-ASCII funny letters in
  1312.  * the Apple character set properly/consistently ... hence the need to
  1313.  * declare s1 and s2 to be type unsigned char *...
  1314.  */
  1315.  
  1316. int zstrcmp (s1, s2)
  1317.   register unsigned char *s1, *s2;
  1318.   {
  1319.     register int n = KEY_LENGTH;
  1320.     
  1321.     for (; --n && *s1 == *s2; s1++, s2++)
  1322.         if (!*s1)
  1323.             break;
  1324.         
  1325.     return (*s1 - *s2);
  1326.   }
  1327.  
  1328.  
  1329. /* function to convert alphanumeric string to a long int, from K&R
  1330.  * but simplified to avoid using isspace() & isdigit() ....
  1331.  */
  1332.  
  1333. long atol (s)
  1334.   register char *s;
  1335.   {
  1336.     int signflag = 0;
  1337.     register long r = 0;
  1338.  
  1339.     while (*s == ' ')
  1340.         s++;
  1341.         
  1342.     if (*s == '-')
  1343.       {
  1344.         signflag = 1;
  1345.         s++;
  1346.       }
  1347.     else if (*s == '+')
  1348.          s++;
  1349.  
  1350.     while (*s >= '0' && *s <= '9') 
  1351.         r = r * 10 + (*s++ - '0');
  1352.     
  1353.     return (signflag ? -r : r);
  1354. }
  1355.  
  1356.  
  1357. /* function to convert a number into a string of width maxDigits and
  1358.  * store it right-justified, blank-filled on left; based on K&R p. 60
  1359.  * example of itoa().
  1360.  *
  1361.  * Error handling:  put a '>' or '<' in leading place to warn of an
  1362.  * overflow (no room for digits on a positive or negative number,
  1363.  * respectively), and put a '^' in leading place to warn if no room
  1364.  * for '-' sign on negative number...
  1365.  */
  1366.  
  1367. void ltoaR (ansp, n, maxDigits)
  1368.   register char *ansp;
  1369.   register long n;
  1370.   int maxDigits;
  1371.   {
  1372.     register int i;
  1373.     long sign;
  1374.     
  1375.     i = maxDigits - 1;
  1376.     if ((sign = n) < 0)
  1377.         n = -n;
  1378.     
  1379.     do
  1380.       {
  1381.           ansp[i--] = n % 10 + '0';
  1382.       }
  1383.     while ((n /= 10) > 0 && i >= 0);
  1384.     
  1385.     if (i < 0 && n > 0)        /* ran out of room with digits still to go */
  1386.       {
  1387.         if (sign > 0)
  1388.             ansp[0] = '>';            /* positive overflow signal */
  1389.         else
  1390.             ansp[0] = '<';            /* negative overflow signal */
  1391.       }
  1392.     else
  1393.       {
  1394.         if (sign < 0)
  1395.             if (i >= 0)
  1396.                 ansp[i--] = '-';
  1397.             else
  1398.                 ansp[0] = '^';        /* no room for '-' sign signal */
  1399.         for ( ; i >= 0; --i)
  1400.             ansp[i] = ' ';
  1401.       }
  1402.  
  1403.     return;
  1404.   }
  1405.   
  1406.